import { getBaseUrl } from '@/utils/http'
import { router } from '@/router'
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'

axios.defaults.withCredentials = true
import qs from 'qs'

export interface BaseResponse<T = any> {
  res: { source_url: string; introduction: string; timestamp: number; sign: string }
  code?: number
  msg?: string
  data: T
}

const ErrorDic: {
  [key: number]: string
} = {
  400: '错误的请求',
  401: '未授权，请登录',
  403: '服务器拒绝访问',
  404: '服务器未找到资源',
  408: '请求超时',
  500: '服务器内部错误',
  502: '网关错误',
  503: '服务不可用',
  504: '网关超时',
}

class ApiError extends Error {
  constructor(public code = 0, message = '') {
    super(message)
    this.name = this.constructor.name
  }

  static createBuiltInApiError(statusCode: number) {
    const message = ErrorDic[statusCode]
    if (message) {
      return new ApiError(statusCode, message)
    } else {
      return new ApiError(statusCode, '抱歉出错了')
    }
  }
}

export enum RequestContentType {
  URL_ENCODE = 0,
  JSON = 1,
  FORM_DATA = 2,
  FORM_URLENCODED = 2,
}

interface MindRequestConfig {
  withCredentials?: boolean
  contentType?: RequestContentType
}

const formDataContentType = 'multipart/form-data'
const formUrlencoded = 'application/x-www-form-urlencoded'
const defaultRequestConfig: AxiosRequestConfig = {
  timeout: 20000,
  headers: {},
}

function getInstanceConfig(instanceConfig: AxiosRequestConfig): AxiosRequestConfig {
  const { timeout, baseURL, headers } = instanceConfig
  let mergedHeaders = defaultRequestConfig.headers
  if (instanceConfig.headers) {
    mergedHeaders = Object.assign({}, defaultRequestConfig.headers, instanceConfig.headers)
  }
  return Object.assign({}, defaultRequestConfig, { timeout, baseURL, mergedHeaders })
}

type InterceptorFulfill<V> = (value: V) => V | Promise<V>
type InterceptorReject = (error: any) => any

export class AjaxRequest {
  private _axiosInstanceConfig: AxiosRequestConfig
  private _ajax: AxiosInstance
  private _concurrencyMap: Map<string, Promise<AxiosResponse<any>>>

  constructor(instanceConfig: AxiosRequestConfig = {}) {
    this._concurrencyMap = new Map()

    this._axiosInstanceConfig = getInstanceConfig(instanceConfig)
    this._ajax = axios.create(this._axiosInstanceConfig)

    this._ajax.interceptors.response.use(
      (res: any) => {
        try {
          if (res && res.status === 200) {
            if (res.data) {
              const { code } = res.data
              if (code === 1004) {
                router.push('/auth/login')
              }
              return res
            } else {
              throw new ApiError(200, '接口返回数据为空')
            }
          } else if (res && res.status === 401) {
            //to do appid
          } else {
            throw new ApiError(0, '抱歉出错了')
          }
        } catch (err) {
          throw err
        }
      },
      (error) => {
        let errorInstance = null
        if (error.response) {
          errorInstance = ApiError.createBuiltInApiError(error.response.status)
        } else {
          errorInstance = new ApiError(0, '抱歉出错了')
        }

        // throw error
        return Promise.reject(errorInstance)
      },
    )
  }

  registerRequestInterceptor(
    fulfilled: InterceptorFulfill<AxiosRequestConfig>,
    rejected: InterceptorReject,
  ) {
    if (fulfilled) {
      this._ajax.interceptors.request.use(fulfilled, rejected)
    }
  }

  registerResponseInterceptor<V>(
    fulfilled: InterceptorFulfill<AxiosResponse<V>>,
    rejected: InterceptorReject,
  ) {
    if (fulfilled) {
      this._ajax.interceptors.response.use(fulfilled, rejected)
    }
  }

  async noRepeatGet<V extends BaseResponse>(
    url: string,
    params: any,
    { withCredentials = true }: MindRequestConfig = {},
  ): Promise<AxiosResponse<V>> {
    const resultParams = params || {}
    resultParams.ts = +new Date()
    const key = qs.stringify({
      url,
      params,
    })
    if (this._concurrencyMap.has(key)) {
      const result = (await this._concurrencyMap.get(key)) as AxiosResponse<V>
      if (result.data.code !== 0) {
        throw new ApiError(result.data.code, result.data.msg)
      } else {
        return result
      }
    } else {
      const pro = this._ajax.get<V, AxiosResponse<V>>(url, {
        params: resultParams,
        withCredentials,
      })
      this._concurrencyMap.set(key, pro)
      const result = await pro
      this._concurrencyMap.delete(key)
      return result
    }
  }

  async get<V extends BaseResponse>(
    url: string,
    params: any,
    { withCredentials = true }: MindRequestConfig = {},
  ): Promise<AxiosResponse<V>> {
    const resultParams = params || {}
    const pro = this._ajax.get<V, AxiosResponse<V>>(url, {
      params: resultParams,
      withCredentials,
    })
    const result = await pro
    return result
  }

  async post<V extends BaseResponse>(
    url: string,
    params: any,
    { withCredentials = true, contentType = RequestContentType.JSON }: MindRequestConfig = {},
  ): Promise<AxiosResponse<V>> {
    let resultParams = params
    if (contentType === RequestContentType.URL_ENCODE) {
      resultParams = qs.stringify(resultParams)
    }
    const httpHeader: any = {}
    if (contentType === RequestContentType.FORM_DATA) {
      httpHeader['Content-Type'] = formDataContentType
    }
    if (contentType === RequestContentType.FORM_URLENCODED) {
      httpHeader['Content-Type'] = formUrlencoded
      resultParams = qs.stringify(params)
    }
    const result = await this._ajax.post<V, AxiosResponse<V>>(url, resultParams, {
      withCredentials,
      headers: httpHeader,
    })
    return result
  }
}

export default new AjaxRequest({
  baseURL: import.meta.env.VITE_API_DOMAIN,
})
